%{
#define YYDEBUG 1
#include <string.h>
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <search.h>

#define BUFF_SZ (2048)
char* buffer = NULL;

#define MONSTER_DATA_FILE ("nhsrc/monst.c")

#define ARRAY_SZ (256)

#define DATE_FORMAT ("%Y/%B/%d\t%T  (%Z %z)")

#define JAVA_FILE_TEMPLATE (\
"/**\n\
* Automatically generated on %s\n\
**/\n\
package termint.game.monsters.all;\n\
import termint.game.Alignment;\n\
import termint.game.monsters.Monster;\n\
import termint.game.monsters.MonsterSymbol;\n\
import termint.game.monsters.Generation;\n\
import termint.game.monsters.MonsterAttack;\n\
import termint.game.monsters.Size;\n\
import termint.game.monsters.Sound;\n\
import termint.game.monsters.Resistance;\n\
import termint.game.monsters.Flag;\n\
import termint.game.monsters.Color;\n\
public class %s extends Monster {\n\
%s\n\
}\n")

char* datestring;

struct pair_s { char name[BUFF_SZ]; char data[BUFF_SZ]; };
typedef struct pair_s pair;

pair *dataptr = NULL;

void standardize_name(char* str);

%}
%union {int i; char* c; struct pair_s* p;};

 /* simple tokens, denoting the start of a section */
%token LVL A ATTK NO_ATTK SIZ 
%token SEDUCTION_ATTACKS  /* hard-coding crap */
%token <i> MON  /* stores the array index, under the assumption that ALL things are included.  This is used for tiles. */ 
 /* tokens representing various #defined constants */
%token <c> SYMBOL   /* ant and bee are S_ANT 'S_*' */
%token <c> GENO     /* monster generation flags 'G_*'  */
%token <c> A_TYPE   /* type of attack 'AT_*' */
%token <c> A_DAMG   /* type of damage 'AD_*' */
%token <c> SOUND    /* monster sound 'MS_*' */
%token <c> SIZE     /* monster size 'MZ_*' */
%token <c> RESIST   /* monster resistances 'MR_*' */
%token <c> MF1 MF2 MF3  /* three sets of flags */
%token <c> COLOR   /* monster symbol color 'CLR_*' */
%token <c> WEIGHT   /* weight constats 'WT_*' */
%token <c> EXTDATA   /* extra memory allocated to this monst? */
%token <c> ALIGNMENT /* alignment strings for nasty monsters 'A_*' */
%token <c> STRING
%token <c> WEIGHT_CONST
%token EMPTY_STRING    /* needed to recognize the end of the array... */
%token <i> NUM
%type <c> str
%type <c> level_data geno_data geno_flags attack_data six_attacks one_attack size_data resist_data resist_list_or_num resist_list conf_resist_data mflag_data3 mflag_data mflag_list mflag ext_data align_data sound_data size_data_x
%type <p> monster
%type <i> weight_data

%initial-action {
	hcreate(10);
}
%%

 /* I'm using asprintf() to leak memory */

input: preamble alldata { YYACCEPT;};

alldata: {}
| monster alldata  {
	FILE* java;
	if ($1 != NULL) {
		if (buffer == NULL) {
			buffer = calloc(BUFF_SZ, sizeof(char));
		} else {
			bzero(buffer, BUFF_SZ);
		}
		dataptr = $1;
		snprintf(buffer, BUFF_SZ, "%s.java", dataptr->name);
 		java = fopen(buffer, "w"); 
 		fprintf(java, JAVA_FILE_TEMPLATE, datestring, dataptr->name, dataptr->data);
 		fclose(java);
 	}
};

preamble: {}
| WEIGHT_CONST NUM preamble { ENTRY e;  e.key = $1; e.data = (void*)$2; hsearch(e, ENTER); fprintf(stdout,"Added %s = %d\n",e.key,$2); }

monster: MON '('  str ',' SYMBOL ',' level_data ',' geno_data ',' attack_data ',' size_data ',' resist_data ',' conf_resist_data ',' mflag_data3 ',' COLOR ')' ','  {
  dataptr = calloc(1, sizeof(pair));

  // color is $25
  strncpy(dataptr->name, $3, BUFF_SZ);
  standardize_name(dataptr->name);
  snprintf(dataptr->data, BUFF_SZ, 
  "\tpublic %s() {\n\t\tsuper(\"%s\",\n\t\t MonsterSymbol.%s, \n\t\t%s, \n\
  \t\t%s, \n\t\t%s, \n\t\t%s, \n\t\t%s, \n\t\t%s, \n\t\t%s,\n\t\tColor.%s,\n\t\t%d /*raw array index*/);\n\t}", dataptr->name, dataptr->name, 
  $5, $7, $9, $11, $13, $15, $17, $19, $21, $1);
  $$ = dataptr;
 }
| MON '(' EMPTY_STRING { $$=NULL; }
;

level_data:  LVL '(' NUM ',' NUM ',' NUM ',' NUM ',' align_data ')' {
  asprintf(& $$, "%d/*lvl*/, %d/*spd*/, %d/*ac*/, %d/*mr*/, %s/*alignment*/", $3, $5, $7, $9, $11);
 };

align_data: NUM {asprintf(& $$, "new Alignment(%d)", $1);}
| ALIGNMENT {asprintf(& $$, "new Alignment(Alignment.Type.%s)", $1);}
;

geno_data: '(' geno_flags '|' NUM ')'  {
  asprintf(& $$, "new Generation[] {%s}, %d", $2, $4);}
| '(' NUM ')' {
  asprintf(& $$, "new Generation[] {}, %d", $2);}
| geno_flags {
  asprintf(& $$, "new Generation[] {%s}, 0", $1);}
| '(' geno_flags ')' {
  asprintf(& $$, "new Generation[] {%s}, 0", $2);};

geno_flags: geno_flags '|' GENO   {
  asprintf(& $$, "%s, Generation.%s", $1, $3);}
| GENO {
  asprintf(&$$, "Generation.%s", $1);};

attack_data: A '(' six_attacks ')' {
  asprintf(&$$, "new MonsterAttack[] {%s}", $3);}
| SEDUCTION_ATTACKS {
  $$="\t\t<Attacks><Attack><Type>BITE</Type><Damage>SSEX</Damage><DNum>0</DNum><DType>0</DType></Attack> <Attack><Type>CLAW</Type><Damage>PHYS</Damage><DNum>1</DNum><DType>3</DType></Attack> <Attack><Type>CLAW</Type><Damage>PHYS</Damage><DNum>1</DNum><DType>3</DType></Attack></Attacks>";};   /* ##BUG: hard-coded */

six_attacks: one_attack ',' one_attack ',' one_attack ',' one_attack ',' one_attack ',' one_attack {
  int n = asprintf(&$$, "%s%s%s%s%s%s", $1, $3, $5, $7, $9, $11);
  if (n > 0)
  	$$[n-1] = '\0';
  };

one_attack: NO_ATTK   {$$=""}
| ATTK '(' A_TYPE ',' A_DAMG ',' NUM ',' NUM ')' {
  asprintf(&$$, "new MonsterAttack(MonsterAttack.Type.%s, MonsterAttack.Damage.%s, %d, %d),", $3, $5, $7, $9);
};

size_data: SIZ '(' weight_data ',' NUM ',' ext_data ',' sound_data ',' size_data_x ')' {
  asprintf(&$$, "%d /*weight*/, %d /*nutrition*/, %s /*special behavior*/, %s, %s",
	   $3, $5, $7, $9, $11);}
weight_data: NUM {$$=$1;}
| WEIGHT {ENTRY e; ENTRY *f;  e.key = $1; f = hsearch(e, FIND);  
			if (!f){fprintf(stderr, "failed to find weight constant for %s\n", e.key); exit(1);} $$ = (int)(f->data); }
ext_data: NUM {assert($1 == 0); $$ = "null";}
| '(' EXTDATA ')' {	if (strcmp($2,"eshk")==0) $$="termint.game.monsters.Behavior.Special.SHOPKEEPER"; 
					if (strcmp($2,"epri")==0) $$="termint.game.monsters.Behavior.Special.PRIEST"; 
					if (strcmp($2,"egd")==0)  $$="termint.game.monsters.Behavior.Special.GUARD"; } 
sound_data: NUM {assert($1 == 0); $$ = "null";}
| SOUND {asprintf(&$$, "Sound.%s", $1);}
size_data_x: NUM {assert($1 == 0); $$ = "null";}
| SIZE {asprintf(&$$, "Size.%s", $1);}

resist_data: resist_list_or_num   {asprintf(&$$, "new Resistance[] {%s} /*resists*/", $1);}
resist_list_or_num: NUM  {assert($1 == 0); $$ = "";}
| resist_list {$$ = $1;}
resist_list: resist_list '|' RESIST {asprintf(&$$, "%s, Resistance.%s", $1, $3);}
| RESIST {asprintf(&$$,"Resistance.%s", $1);}
conf_resist_data: resist_list_or_num  {asprintf(&$$, "new Resistance[] {%s} /*conferred*/", $1);}

mflag_data3: mflag_data ',' mflag_data ',' mflag_data  { char* c=NULL;
														 int i = asprintf(&c, "%s%s%s", $1, $3, $5);
														 if (i>0) c[i-1]='\0';
														 asprintf(&$$, "new Flag[] {%s}", c); }
mflag_data: NUM {assert($1 == 0); asprintf(&$$, "");}
| mflag_list {$$=$1}
mflag_list: mflag_list '|' mflag  {asprintf(&$$, "%s Flag.%s,", $1, $3);}
| mflag  {asprintf(&$$,"Flag.%s,", $1);}
mflag: MF1 | MF2 | MF3

str: STRING  {$$ = $1;}
;

%%
extern int yylineno;
extern char* yytext;
int yyerror(char *s, int lineNumber) {
  fprintf(stderr, "Error: %s at line number %d '%s'\n", s, yylineno, yytext);
  return 0;
}

// take a string and make it conform to Java standards: capitalize words, turn spaces into underscores
void standardize_name(char* str) {
	int saw_space = 1;
	int i;
	for (i = 0; i < BUFF_SZ && str[i] != '\0'; i++) {
		if (isalpha(str[i])) {
			if (saw_space) {
				saw_space = 0;
				if (islower(str[i])) {
					str[i] = toupper(str[i]);
				}
			}
		} else if (isspace(str[i])) {
			saw_space = 1;
			str[i] = '_';
		} else if (str[i] == '-') {
			str[i] = '_';
		} else {
			fprintf(stderr, "Fatal error: converting a monster name with an unrecognized char: %s: %d", str, i);
			exit(1);
		} 
	}
}

int main(int argc, char**argv) {
yydebug=0;
	time_t curtime = time((time_t*)0);
	datestring = calloc(ARRAY_SZ, sizeof(char));
	strftime(datestring, ARRAY_SZ, DATE_FORMAT, localtime(&curtime));

	// parse the monsters
	FILE* mondat_file = fopen(MONSTER_DATA_FILE, "r");
	yyrestart(mondat_file);
	yyparse();
	fclose(mondat_file);
}
